from PyQt4.QtCore import QObject

from random import randint

from config import *
from autoplayer.neighbour import has_neighbour
from autoplayer.zobrist import Zobrist
from game.judge import judge_winner


class Checkmater:
    def __init__(self):
        self._cache = {}
        self.evaluator = None
        self.max_score = SCORE.THREE
        self.min_score = SCORE.FOUR
        self.role = None
        self.model = None
        self.simulate_model = None
        self.zobrist = None

    def setup(self, role, model, zobrist, evaluator):
        self._cache = {}
        self.evaluator = evaluator
        self.max_score = SCORE.THREE
        self.min_score = SCORE.FOUR
        self.role = role
        self.model = model
        self.simulate_model = model
        self.zobrist = zobrist

    def cache(self, deep, result):
        if not CACHE:
            return
        self.cache[self.zobrist.code] = {
            'deep': deep,
            'result': result
        }

    def find_max(self, role, score_argu):
        result = []
        for i in range(0, Len):
            for j in range(0, Len):
                if self.model.get_pos(i, j) != ROLES.vacant:
                    continue
                point = [i, j, 0]
                if not has_neighbour(self.model, i, j, 2, 1):
                    continue
                if role == self.role:
                    score = self.evaluator.autoplayer_score[i][j]
                else:
                    score = self.evaluator.manplayer_score[i][j]
                point[2] = score
                if score > SCORE.FIVE:
                    return [point]
                if score > score_argu:
                    result.append(point)
        sorted(result, reverse=True)
        return result
    # result = [point...]; point = [i, j, score]

    def find_min(self, role, score_argu):
        result = []
        fives = []
        fours = []
        for i in range(0, Len):
            for j in range(0, Len):
                if self.model.get_pos(i, j) != ROLES.vacant:
                    continue
                point = [i, j, 0]
                if not has_neighbour(self.model, i, j, 2, 1):
                    continue
                if role == self.role:
                    auto_score = self.evaluator.autoplayer_score[i][j]
                    man_score = self.evaluator.manplayer_score[i][j]
                else:
                    auto_score = self.evaluator.manplayer_score[i][j]
                    man_score = self.evaluator.autoplayer_score[i][j]
                if auto_score >= SCORE.FIVE:
                    point[2] = -auto_score
                    return [point]
                elif auto_score >= SCORE.FOUR:
                    point[2] = -auto_score
                    fours.insert(0, point)
                    continue
                if man_score >= SCORE.FIVE:
                    point[2] = man_score
                    fives.append(point)
                    continue
                elif man_score >= SCORE.FOUR:
                    point[2] = man_score
                    fours.append(point)
                    continue
                if auto_score > score_argu or man_score > score_argu:
                    # point = {'i': i, 'j': j}
                    point[2] = auto_score
                    result.append(point)
        if fives.__len__() > 0:
            return [fives[0]]
        if fours.__len__() > 0:
            return [fours[0]]
        sorted(result, key=lambda x: abs(x[2]), reverse=True)
        return result
        # result = [point...]; point = [i, j, score]

    def get_min(self, role, deep):
        winner = judge_winner(self.model)
        if winner == role:
            return True
        if winner == opposite_role(role):
            return False
        if deep <= 0:
            return False
        if CACHE:
            cache_val = self._cache[self.zobrist.code]
            if cache_val:
                if cache_val['deep'] >= deep and cache_val['result']:
                    return cache_val['result']
        points = self.find_min(opposite_role(role), self.min_score)
        if points.__len__() == 0:
            return False
        if -points[0][2] >= SCORE.FOUR:
            return False
        candidates = []
        current_role = opposite_role(role)
        for point in points:
            x = point[0]
            y = point[1]
            self.simulate_model.new_step(x, y)
            max_points = self.find_max(role, deep - 1)
            self.simulate_model.retract()
            if max_points:
                max_points.insert(0, point)
                candidates.append(max_points)
                self.cache(deep, max_points)
                continue
            else:
                self.cache(deep, False)
                return False
        result = candidates[randint(0, candidates.__len__())]
        self.cache(deep, result)
        return result

    def get_max(self, role, deep):
        if deep <= 0:
            return False
        if CACHE:
            cache_val = self._cache.get(self.zobrist.code)
            if cache_val and cache_val['deep'] >= deep or cache_val['result']:
                return cache_val['result']
        points = self.find_max(role, self.max_score)
        if not points.__len__() > 0:
            return []
        if points.__len__() > 0 and points[0][2] > SCORE.FOUR:
            return points[0]
        for point in points:
            x = point[0]
            y = point[1]
            self.simulate_model.new_step(x, y)
            min_points = self.find_min(role, deep - 1)
            self.simulate_model.retract()
            if isinstance(min_points, list):
                if min_points.__len__() > 0:
                    min_points.insert(0, point)
                    self.cache(deep, min_points)
                    return min_points
                else:
                    self.cache(deep, [point])
                    return [point]
        self.cache(deep, False)
        return False
    # result = [point, ...], point = [i, j, score]

    def deepen(self, role, deep):
        for i in range(1, deep + 1):
            result = self.get_max(role, i)
            if result:
                return result

    def check(self, role, deep):
        max_score = SCORE.FOUR
        min_score = SCORE.FIVE
        result = self.deepen(role, deep)
        if result:
            result = {
                'points': result,
                'score': SCORE.FOUR
            }
            return result
        max_score = SCORE.THREE
        min_score = SCORE.FOUR
        result = self.deepen(role, deep)
        if result:
            result = {
                'points': result,
                'score': SCORE.THREE * 2
            }
            return result
    # result = {score, points} points = [i, j, ...]






